# data vizlibrary(ggplot2)library(ggh4x)library(ggOceanMaps)library(patchwork)library(viridis)library(ggpubr)library(grid)library(viridis)library(ggnewscale)# tablelibrary(gt)# maplibrary(ggmap)library(ggsn)library(sf)library(sp)library(smoothr)# kernel densitylibrary(eks)library(ggdensity)# statlibrary(Hmisc)library(circular)library(CircStats)# charlibrary(stringr)# solar anglelibrary(oce)# data wranglinglibrary(tidyr)library(dplyr)library(purrr)library(forcats)library(lubridate)
2 Setting up custom function
2.1windrose
Code
windrose <-function(data_to_plot,grid =NULL,set_title =NULL,legend_position ="none",facet = F) {# this code comes from Roxanne uniqhours <-1:24* (360/24)# trick to align hours om the graph data_to_plot <-rbind(data_to_plot[2:nrow(data_to_plot), ], data_to_plot[1, ])for (i in1:24) {# turn hours to radiansif (i ==1) { temp <-rep(uniqhours[i], data_to_plot$nb_ind_hour[i]) day_night <-rep("night", data_to_plot$nb_ind_hour[i]) } else { temp <-c(temp, rep(uniqhours[i], data_to_plot$nb_ind_hour[i])) day_night <-c(day_night, rep(if_else(between(i, 7, 20), "day", "night"), data_to_plot$nb_ind_hour[i] )) } } data2 <-data.frame(direction = temp) deg <-15# choose bin size (degrees/bin) dir.breaks <-seq(0- (deg /2), 360+ (deg /2), deg) # define the range of each bin# assign each direction to a bin range dir.binned <-cut(data2$direction,breaks = dir.breaks,ordered_result =TRUE )# generate pretty lables dir.labels <-as.character(c(seq(0, 360- deg, by = deg), 0))# replace ranges with pretty bin lableslevels(dir.binned) <- dir.labels# Assign bin names to the original data set data2$dir.binned <- dir.binned# add origin if anyif (facet) { data2$origin <-unique(data_to_plot$origin) }# set up max value maxvalue <-35# initialise the plot plt.dirrose_2 <-ggplot()# check if gridif (!is.null(grid)) { plt.dirrose_2 <- plt.dirrose_2 +geom_hline(yintercept = grid,colour ="grey20",linewidth = .2 ) } plt.dirrose_2 <- plt.dirrose_2 +geom_vline(xintercept =c(seq(1, 24, 2)),colour ="grey30",linewidth =0.2 ) +# 24 vertical lines at center of the 30? ranges.geom_hline(yintercept = maxvalue,colour ="white",linewidth = .5 ) +# Darker horizontal line as the top border (max).# On top of everything we place the histogram bars.geom_bar(data = data2,aes(x = dir.binned, fill = day_night),width =1,colour ="black",linewidth =0.3 ) +# geom_bar(data = data2, aes(x = dir.binned2), width = 1, colour="black", size = 0.3,fill="salmon",alpha=0.9) +scale_x_discrete(drop =FALSE,labels =c(0, "", 2, "", 4, "", 6, "", 8, "", 10, "", 12, "", 14, "", 16, "", 18, "", 20, "", 22, "" ) ) +scale_fill_manual(values =c("white", "darkgrey"), name ="Time of day") +labs(x ="Time (hours)", y ="Count", title = set_title) +coord_polar(start =-(deg /2) * (pi /180))# if facetif (facet) { plt.dirrose_2 <- plt.dirrose_2 +facet_wrap2(. ~ origin) }# Wraps the histogram into a windrose plt.dirrose_2 <- plt.dirrose_2 +theme_bw() +theme(legend.position = legend_position,panel.grid.minor =element_blank(),panel.grid.major =element_blank(),panel.background =element_blank(),legend.key.size =unit(0.5, "cm") )# returnreturn(plt.dirrose_2) }
3 Import data
Let’s load data_dive, i.e. the output of data_wrangling.qmd, and filter only on animals leaving from Ano Nuevo.
Code
# import the datadata_dive <-readRDS("../export/data_dive.rds")# filter on seals departing from ANNUdata_dive <- data_dive %>%filter(DepartureLocation =="ANNU")
Code
# get solar angle when data locationsolar_angle_inter <- data_dive %>%filter(!is.na(Lat)) %>%mutate(solar_angle =sunAngle(date, Long, Lat)$altitude) %>%select(id, DiveNumber, solar_angle)# merge that with data_divedata_dive <-merge(data_dive, solar_angle_inter,by =c("id", "DiveNumber"),all.x = T)# change few thingsdata_dive <- data_dive %>%mutate(# add day-nightday_night =if_else(solar_angle <-18, "night", "day"),# change divetypeDiveType = data.table::fifelse(DiveType ==3& (abs(bathy) - Maxdepth) >100, 0, DiveType),DiveTypeName = data.table::fifelse( DiveTypeName =="Benthic"& (abs(bathy) - Maxdepth) >100,"Transit", DiveTypeName ) )
Let’s create a table specific for this figure that only contains for each individual:
the first dive
the last dive
all benthic dives
Code
# let's add a column with the local_timedata_windrose <- data_dive %>%# get rid of data without location informationfilter(!is.na(Lat)) %>%# then by individualgroup_by(id) %>%# keep only the first, last date, but also all benthic divesfilter(date ==min(date) | date ==max(date) | DiveTypeName =="Benthic")
Code
hour_time_2_radian <-function(x, high =24, low =0) {# https://pingouin-stats.org/build/html/generated/pingouin.convert_angles.html#pingouin.convert_angles ptp <- high - low rad <- x * (2* pi) / ptp rad <- (rad + pi) %% (2* pi) - pireturn(rad)}
Figure 1: Circular histogram plots displaying the times (in hours) of when female northern elephant seals (n = 403) perform their first recorded dive upon departing for their foraging trip (a), the last dive performed before returning from their foraging trip (b), and the time when they perform all benthic dives across their foraging trip (c).
# data use to compute kernel density estimationdf_kernel_dens <- data_dive %>%# only with location datafilter(!is.na(Lat)) %>%# select only the required columnsselect(lat = Lat, long = Long, id, DiveTypeName) %>%# create col to nicely display dive typemutate(origin =paste(DiveTypeName, "dives"))
Code
# check if background_ggoceanmaps existif (file.exists("../export/background_ggoceanmap.rds")) { trip <-readRDS("../export/background_ggoceanmap.rds")} else {# using ggOceanMaps trip <-basemap(limits =c(170, -110, 20, 59),bathymetry =TRUE,shapefiles ="Arctic",rotate =TRUE,grid.col =NA )# Make the graticules: lims <-attributes(trip)$limits graticule <- sf::st_graticule(c(lims[1], lims[3], lims[2], lims[4]),crs =attributes(trip)$proj,lon =seq(-180, 180, 45),lat =seq(-90, 90, 10) )# Plot trip <- trip +geom_sf(data = graticule, color ="grey50") trip$layers <- trip$layers[c(1, 3, 2)]# save resultsaveRDS(trip, "../export/background_ggoceanmap.rds")}
Code
# transform data into sf objectdf_kernel_dens_sf <-st_as_sf( df_kernel_dens,coords =c("long", "lat"),crs =st_crs(4326))# make it's group_by origindf_kernel_dens_sf <-group_by(df_kernel_dens_sf, origin)# kernel density estimationdf_kernel_dens_sf_kde <-st_kde(df_kernel_dens_sf,H =diag(c( MASS::bandwidth.nrd( sf::st_coordinates(df_kernel_dens_sf)[, 1] ), MASS::bandwidth.nrd( sf::st_coordinates(df_kernel_dens_sf)[, 2] ) ) /4)^2)# https://github.com/r-spatial/sf/issues/1762sf::sf_use_s2(FALSE)# plottrip +# new scalenew_scale_fill() +# kernelgeom_sf(data =st_get_contour(# geospatial kernel df_kernel_dens_sf_kde,# probabilitiescont =c(50, 80, 95, 99) ),# displayaes(fill =label_percent(contlabel)),alpha =0.7 ) +# same colour barscale_fill_viridis_d(option ="plasma") +# legendlabs(fill ="Probs") +# no display alphaguides(alpha ="none") +# facet by originfacet_wrap(. ~ origin)
Figure 2: Kernel density plots of the dives performed by female northern elephant seals (n = 403) during their foraging trips separated by each of the four dive types. Level indicates the concentration of dives with red showing the highest concentration. Seals all departed and returned from Año Nuevo State Park.
Next step
see if it worth adding the North arrow and a scale
Using ggdensity instead of eks
This was the original way, but due to packages updates, I decided to switch with eks + ggOceanMaps option.
Code
# register to query google APIregister_google(key =readChar("../key/api_key.txt",file.info("../key/api_key.txt")$size))# get the maptrip_bis <-qmplot( long, lat,# weird, doesn't work if I increase nb of locations...data = df_kernel_dens %>%group_by(origin) %>%filter(row_number() <1000),geom ="blank",zoom =3,maptype ="satellite",source ="google")# mapstrip_bis +# kernelgeom_hdr(data = df_kernel_dens,aes(x = long, y = lat, fill =after_stat(probs)) ) +# remove some legendguides(alpha ="none") +labs(fill ="Probs") +# facetfacet_wrap2(. ~ origin)
Figure 3: Same but performed with ggdensity package
Figure 4: Overlap of female northern elephant seal’s trip (n=424) and predator habitat along with the percentage of benthic dives occurring inside as compared to outside of the habitat. The white star indicates the location of Año Nuevo State Park. Map a) displays orca and shark habitat along the west coast (adapted from Jorgenson, et al. 2019) and black lines represent the trip of all female northern elephant seals studied. Bar graph displays the average percentage of benthic dives occurring either inside or outside of orca habitat and displays the average percentage of benthic dives occurring inside and outside of shark habitat.
Next steps
see if it worth adding the North arrow and a scale
add the result of the test (inside vs. outside)
do we actually need legend for the map (area + bathymetry)?
4.5 Figure 4
Code
# main plotbathy_prop <- data_dive %>%# keep only negative bathyfilter(bathy <0) %>%# and remove outliersfilter(bathy >-6000) %>%# create class of bathymetrymutate(bathy_class =fct_rev(cut( bathy,seq(-6000, 0, 400),ordered_result = T,dig.lab =4 ))) %>%# calculate by bath_class and animalgroup_by(bathy_class, DiveTypeName) %>%# the number of divessummarise(N =n(), .groups ="drop") %>%# and divide by the total number of dives per bathy_classgroup_by(bathy_class) %>%# to get the percentage of different dive types per bathy_classmutate(percentage = N /sum(N)) %>%# ungroup => not required but that let the dataset cleanungroup() %>%# then plotggplot(aes(x = bathy_class, y = percentage)) +# the areageom_area(aes(fill = DiveTypeName, group = DiveTypeName)) +# orientation of x labelsguides(x =guide_axis(angle =45)) +# format y axisscale_y_continuous(labels =function(x) {paste0(x *100, "%") } ) +labs(x ="Bathymetry (m)",y ="Dive type proportion (%)" ) +scale_fill_viridis("Dive Type",option ="plasma",discrete = T,direction =-1 ) +# scale_fill_manual("Dive Type", values = c("#fcfdbf", "#fc8961", "#b73779", "#51127c"))# position legend at the toptheme(legend.position ="none",panel.grid.major =element_blank(),panel.grid.minor =element_blank(),panel.background =element_blank(),axis.line =element_line(arrow =arrow(angle =20,length =unit(.10, "inches"),type ="closed" )) )# second plotbathy_hist <- data_dive %>%# keep only negative bathyfilter(bathy <0) %>%# and remove outliersfilter(bathy >-6000) %>%# create class of bathymetrymutate(bathy_class =fct_rev(cut( bathy,seq(-6000, 0, 400),ordered_result = T,dig.lab =4 ))) %>%# calculate by bath_class and animalgroup_by(bathy_class, DiveTypeName) %>%# the number of divessummarise(N =n(), .groups ="drop") %>%# ggplotggplot(aes(x = bathy_class,y = N,fill = DiveTypeName,width =0.5 )) +geom_bar(stat ="identity",position ="dodge" ) +scale_fill_viridis(option ="plasma",discrete = T,direction =-1 ) +theme_void() +theme(legend.position ="top")# the actual plotbath_plot <- bathy_hist / bathy_prop +plot_layout(heights =c(1, 10))
Code
# main plotdist_coast_prop <- data_dive %>%# filter out animal without dist_coastfilter(dist_coast >0) %>%# remove outliersfilter(dist_coast *1000*111<=1900) %>%# create class of bathymetrymutate(dist_class =cut(# convert decimal degree/1000 dist_coast *1000*111,seq(0, 1900, 100),ordered_result = T,dig.lab =4 )) %>%# calculate by bath_class and animalgroup_by(dist_class, DiveTypeName) %>%# the number of divessummarise(N =n(), .groups ="drop") %>%# and divide by the total number of dives per dist_classgroup_by(dist_class) %>%# to get the percentage of different dive types per dist_classmutate(percentage = N /sum(N)) %>%# ungroup => not required but that let the dataset cleanungroup() %>%# then plotggplot(aes(x = dist_class, y = percentage)) +# the areageom_area(aes(fill = DiveTypeName, group = DiveTypeName)) +# orientation of x labelsguides(x =guide_axis(angle =45)) +# format y axisscale_y_continuous(labels =function(x) {paste0(x *100, "%") } ) +labs(x ="Distance from the coast (km)",y ="Dive type proportion (%)" ) +scale_fill_viridis("Dive Type",option ="plasma",discrete = T,direction =-1 ) +# scale_fill_manual("Dive Type", values = c("#fcfdbf", "#fc8961", "#b73779", "#51127c"))# position legend at the toptheme(legend.position ="none",panel.grid.major =element_blank(),panel.grid.minor =element_blank(),panel.background =element_blank(),axis.line =element_line(arrow =arrow(angle =20,length =unit(.10, "inches"),type ="closed" )) )# the second plotdist_coast_hist <- data_dive %>%# filter out animal without dist_coastfilter(dist_coast >0) %>%# remove outliersfilter(dist_coast *1000*111<=1900) %>%# create class of bathymetrymutate(dist_class =cut(# convert decimal degree/1000 dist_coast *1000*111,seq(0, 2000, 100),ordered_result = T,dig.lab =4 )) %>%# calculate by bath_class and animalgroup_by(dist_class, DiveTypeName) %>%# the number of divessummarise(N =n(), .groups ="drop") %>%# ggplotggplot(aes(x = dist_class,y = N,fill = DiveTypeName,width =0.5 )) +geom_bar(stat ="identity",position ="dodge" ) +scale_fill_viridis(option ="plasma",discrete = T,direction =-1 ) +theme_void() +theme(legend.position ="top")# the actual plotdist_coast_plot <- dist_coast_hist / dist_coast_prop +plot_layout(heights =c(1, 10))
Code
# the main plotdist_dep_prop <- data_dive %>%# filter out animal without dist_coastfilter(dist_dep >0) %>%# remove outliersfilter(dist_dep /1000<4200) %>%# create class of bathymetrymutate(dist_class =cut( dist_dep /1000,seq(0, 4200, 300),ordered_result = T,dig.lab =4 )) %>%# calculate by bath_class and animalgroup_by(dist_class, DiveTypeName) %>%# the number of divessummarise(N =n(), .groups ="drop") %>%# and divide by the total number of dives per dist_classgroup_by(dist_class) %>%# to get the percentage of different dive types per dist_classmutate(percentage = N /sum(N)) %>%# ungroup => not required but that let the dataset cleanungroup() %>%# then plotggplot(aes(x = dist_class, y = percentage)) +# the areageom_area(aes(fill = DiveTypeName, group = DiveTypeName)) +# orientation of x labelsguides(x =guide_axis(angle =45)) +# format y axisscale_y_continuous(labels =function(x) {paste0(x *100, "%") } ) +labs(x ="Distance from departure (km)",y ="Dive type proportion (%)" ) +scale_fill_viridis("Dive Type",option ="plasma",discrete = T,direction =-1 ) +# scale_fill_manual("Dive Type", values = c("#fcfdbf", "#fc8961", "#b73779", "#51127c"))# position legend at the toptheme(legend.position ="none",panel.grid.major =element_blank(),panel.grid.minor =element_blank(),panel.background =element_blank(),axis.line =element_line(arrow =arrow(angle =20,length =unit(.10, "inches"),type ="closed" )) )# the second plotdist_dep_hist <- data_dive %>%# filter out animal without dist_coastfilter(dist_coast >0) %>%# remove outliersfilter(dist_dep /1000<4200) %>%# create class of bathymetrymutate(dist_class =cut( dist_dep /1000,seq(0, 4200, 300),ordered_result = T,dig.lab =4 )) %>%# calculate by bath_class and animalgroup_by(dist_class, DiveTypeName) %>%# the number of divessummarise(N =n(), .groups ="drop") %>%# ggplotggplot(aes(x = dist_class,y = N,fill = DiveTypeName,width =0.5 )) +geom_bar(stat ="identity",position ="dodge" ) +scale_fill_viridis(option ="plasma",discrete = T,direction =-1 ) +theme_void() +theme(legend.position ="top")# the actual plotdist_dep_plot <- dist_dep_hist / dist_dep_prop +plot_layout(heights =c(1, 10))
Figure 5: Proportion of dives performed across entire trip separated by dive type across bathymetry intervals and distance from departure intervals, and displays the proportion of benthic dives occurring over the seals time spent at sea. For graph the largest percentage of benthic dives occurs at the lowest depth then occurs again at higher depths. For graph The highest percentage of benthic dives occurs at the lowest distance from where the seal is first assumed to have departed.
Next step
Try to keep a similar number of classes among figures
4.6 Figure 5
Code
# the last plotprop_at_sea <- data_dive %>%group_by(id) %>%# time difference between the first date and the others (telling R to take diff b/w first date and all other dates)mutate(nb_days_departure =trunc(as.numeric(difftime( date, first(date),units ="days" )))) %>%# group by day/dive_type/sealgroup_by(nb_days_departure, DiveTypeName, id) %>%# count number of divessummarise(nb_daily_dives_type =n(), .groups ="drop") %>%# group by day/sealgroup_by(nb_days_departure, id) %>%# count the total number of dives per day/sealmutate(nb_daily_dives =sum(nb_daily_dives_type)) %>%# calculation of the proportionmutate(prop = nb_daily_dives_type / nb_daily_dives) %>%# order by seals/datearrange(id, nb_days_departure) %>%# perform our calculation per sealgroup_by(id) %>%# calculate of the percentage of time since departuremutate(perc_time_at_sea =round(nb_days_departure /max(nb_days_departure) *100, 1)) %>%# focus on benthic divesfilter(DiveTypeName =="Benthic") %>%# add class of percentage of time at seamutate(day_class =cut(perc_time_at_sea, seq(0, 100, 5),include.lowest = T )) %>%# by class of percentage of time at seagroup_by(day_class) %>%# calculate the proportion of daily benthic divessummarise(nb_benthic_class =sum(nb_daily_dives_type),nb_total_class =sum(nb_daily_dives),prop_class = nb_benthic_class / nb_total_class ) %>%# plotggplot( .,aes(x = day_class, y = prop_class) ) +geom_bar(stat ="identity", fill ="grey", col ="grey30") +guides(x =guide_axis(angle =45)) +scale_y_continuous(labels =function(x) { scales::percent(abs(x), 1) } ) +theme(panel.grid.major =element_blank(),panel.grid.minor =element_blank(),panel.background =element_blank(),axis.line =element_line(arrow =arrow(angle =20,length =unit(.10, "inches"),type ="closed" )) ) +labs(x ="Time spent at sea (%)",y ="Daily proportion of benthic dives (%)" )
Code
# displayprop_at_sea
Figure 6: Distribution of the percentage of benthic dives during elephant seals’ trip to sea expressed as a percentage of time spent at sea.